package ms.unicorn.dsl.runtime;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.Status;

import ms.unicorn.dsl.DslPluginActivator;
import ms.unicorn.dsl.runtime.impl.DefaultExecutionContext;
import ms.unicorn.dsl.runtime.impl.XObjectLiteral;
import ms.unicorn.dsl.runtime.interpreter.XBasicOperatorInterpreter;
import ms.unicorn.dsl.runtime.interpreter.XControlFlowInterpreter;
import ms.unicorn.dsl.runtime.interpreter.XFeatureCallInterpreter;
import ms.unicorn.dsl.runtime.interpreter.XFunctionDeclartationInterpreter;
import ms.unicorn.dsl.runtime.interpreter.XLiteralInterpreter;
import ms.unicorn.dsl.unicornDSL.FunctionArgument;
import ms.unicorn.dsl.unicornDSL.IdentifiedElement;
import ms.unicorn.dsl.unicornDSL.UnicornLanguage;
import ms.unicorn.dsl.unicornDSL.XExpression;
import ms.unicorn.dsl.unicornDSL.XFunctionDeclaration;
import ms.unicorn.dsl.utils.ExceptionUtil;
import ms.unicorn.dsl.utils.ReflectionUtil;
import ms.unicorn.dsl.utils.SphynxInterpreterMissingException;
import ms.unicorn.dsl.utils.SphynxRuntimeException;
import ms.unicorn.dsl.utils.SphynxTypeMismatchException;

public class SphynxInterpreter {
	
	public static SphynxInterpreter INSTANCE = new SphynxInterpreter();
	
	private List<Class<?>> interpreters;
	private Map<Class<?>, Method> expressionIntepreters;
	
	public SphynxInterpreter() {
		interpreters = new ArrayList<Class<?>>();
		expressionIntepreters = new HashMap<Class<?>, Method>();
		
		registerInterpreter(XFunctionDeclartationInterpreter.class);
		registerInterpreter(XLiteralInterpreter.class);
		registerInterpreter(XBasicOperatorInterpreter.class);
		registerInterpreter(XFeatureCallInterpreter.class);
		registerInterpreter(XControlFlowInterpreter.class);
		registerInterpreter(XObjectLiteral.class);
	}
	
	public void registerInterpreter(Class<?> interpreterClass) {
		interpreters.add(interpreterClass);
	}
	
	protected Method getExpressionInterpreter(XExpression expression) {
		if (expression == null)
			throw new RuntimeException("expression is null");
		Method med = expressionIntepreters.get(expression.getClass());
		if (med == null) {
			// look for expression interpreter from registered interpreters
			String methodName = "_evaluate";
			Class<?> params[] = new Class<?>[] {SphynxInterpreter.class, expression.getClass(), ExecutionContext.class };
			for (Class<?> clazz: interpreters) 
				if ((med = ReflectionUtil.getMethod(clazz, methodName, params)) != null)
					break;
			if (med != null)
				expressionIntepreters.put(expression.getClass(), med);
		}
		return med;
	}
	
	public void execute(UnicornLanguage sphynxProgram, ExecutionContext context, Map<String, Object> arguments) {
		try {
			if (context == null)
				context = new DefaultExecutionContext(null, null);
			XFunctionDeclaration main = null;
			for(XExpression element: sphynxProgram.getElements()) 
				if (element instanceof XFunctionDeclaration) {
					XFunctionDeclaration funcdef = (XFunctionDeclaration) element;
					if ("main".equals(funcdef.getName())) {
						main = funcdef;
						break;
					}
				}
			if (main != null) {
				ExecutionContext ctx = context.createChildContext();
				for (String key: arguments.keySet())
					ctx.allocateVariable(VariableStorage.ARGUMENT, false, key, arguments.get(key));
						
				for (IdentifiedElement e: main.getArguments()) {
					FunctionArgument arg = (FunctionArgument)e;
					if (ctx.getVariable(arg.getName(), false, false)==null) {
						if (arg.getValue() != null)
							ctx.allocateVariable(VariableStorage.ARGUMENT, false, arg.getName(), evaluate(arg.getValue(), ctx));
						else
							throw new SphynxRuntimeException(ctx, arg, "Parameter is not set: " + arg.getName());
					}
				}						
				//evaluate(main, ctx);
				XFunctionDeclartationInterpreter.interpret(this, main, ctx);
			}
			else
				throw new SphynxRuntimeException(context, null, "Unable to find main() function in program '" + sphynxProgram.getName() + "'");
		} catch (Exception e) {
			ExceptionUtil.handleException(e);
		}
	}

	
	
	public Object evaluate(XExpression sphynxExpression, ExecutionContext context) {
		Method med = getExpressionInterpreter(sphynxExpression);
		XExpression lastExpression = context.getEvaluatingExpression();
		Object result = null;
		//try {
		context.setEvaluatingExpression(sphynxExpression);
		if (med != null)
			result = ReflectionUtil.invoke(med, null, this, sphynxExpression, context);
		else
			//throw new SphynxRuntimeException(context, null, "Unable to interpete expression of type '" + sphynxExpression.getClass().getName() + "'");
			throw new SphynxInterpreterMissingException(context, sphynxExpression);

		context.setEvaluatingExpression(lastExpression);			
		return result;
		/*}
		catch(Exception e) {
			String msg = ExceptionUtil.handleException(e);
			if ( DslPluginActivator.getDefault() != null) {
				DslPluginActivator.getDefault().log(Status.ERROR, msg);
			}
		}
		return null;*/
	}
	
	public Object resolveValue(Object obj) {
		while (obj instanceof VariableStorage)
			obj = ((VariableStorage) obj).getValue();
		return obj;
	}
	
	public Object resolveValue(XExpression exp, ExecutionContext context) {
		return resolveValue(evaluate(exp, context));
	}
	
	public Object resolveValue(XExpression exp, ExecutionContext context, Class<?> expected) {
		Object res = resolveValue(exp, context);
		if (!expected.isInstance(res))
			throw new SphynxTypeMismatchException(context, exp, expected, res);
		return res;
	}
}
